/*
 * Copyright (c) 2008, Yifu Huang
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of SleekIce nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.sleekice.core;

/**
 * This handles the framerate of the game by providing a way to limit the
 * frames per second.
 */
public class FpsHandler {
    public static final double DEF_FPS_LIMIT = 50.0;
    public static final int DEF_FPS_ACCURACY = 100;

    private double[] fps;
    private int index; // the current index of fps[]
    private double fpsLimit;
    private double fpsCurrent = 0.0;
    private long startFrameTime;

    /**
     * Creates the handler with the default framerate limit and accuracy.
     */
    public FpsHandler() {
        this(DEF_FPS_LIMIT);
    }
    /**
     * Creates the handler with given framerate limit and default accuracy.
     */
    public FpsHandler(double fpsLimit) {
        this(fpsLimit, DEF_FPS_ACCURACY);
    }
    /**
     * Create the handler with given framerate limit and accuracy.
     */
    public FpsHandler(double fpsLimit, int accuracy) {
        if (accuracy <= 0) {
            System.err.println("FPSCounter Error: Bad constructor initialization");
        }

        /* establish an array of fps calculations
         * the more calculations the more accurate the averageFPS will be
         */
        fps = new double[accuracy];
        for (int i = 0; i < fps.length;
            i++) {
            fps[i] = 0;
        }
        index = 0;

        this.fpsLimit = fpsLimit;
    }

    public void setFPSLimit(double fps) {
        fpsLimit = fps;
    }

    /* the start of a new frame */
    public void startFrame() {
        startFrameTime = System.nanoTime();
    }

    /* the end of a frame, this is where it actually does stuff */
    public void endFrame() {
        /* convert nanoseconds to seconds */
        double timeDiff =
            (double) (System.nanoTime() - startFrameTime) / 1000000000;
        /* if it took 0 seconds */
        if (timeDiff == 0) {
            timeDiff = 0.000000001; /* set it to 1 nano second */
        }
        fpsCurrent = 1 / timeDiff; /* get fps */
        try {
            sleep(); /* sleep to maintain desired fps */
        }
        catch (InterruptedException ie) {
            ie.printStackTrace();
        }

        /* Get the fps after sleeping. This fps should be close to the desired
         * fps because the thread sleeps accordingly. One possible explanation
         * for the small error is that only ms can be used in sleep which is
         * less accurate than ns.
         */
        timeDiff =
            (double) (System.nanoTime() - startFrameTime) / 1000000000;
        if (timeDiff == 0) {
            timeDiff = 0.000000001;
        }
        fps[index] = 1 / timeDiff;
        /* renew index accordingly */
        if (index < fps.length - 1) {
            index++;
        }
        else {
            index = 0;
        }
    }

    /* gets the average fps */
    public double getAverageFPS() {
        /* add up all the averageFPS calculations
         * once 0 or end of array is found, break
         */
        int i;
        double sum = 0;
        for (i = 0; i < fps.length;
            i++) {
            if (fps[i] == 0) {
                break;
            }
            sum = sum + fps[i];
        }

        /* don't divide by 0 */
        if (i > 0) {
            return sum / i;
        }
        else {
            return 0;
        }
    }

    /* sleeps a certain amount depending on your fpsCurrent and your fpsLimit */
    private double sleepMilli;
    private int sleepNano;
    private void sleep() throws InterruptedException {
        if (fpsCurrent > fpsLimit) {
            sleepMilli = 1000 / fpsLimit - 1000 / fpsCurrent;
            /* If the sleepMilli is negative, don't sleep.
             * This usually means that the fps is very low and all the cpu
             * is needed.
             */
            if (sleepMilli > 0) {
                sleepNano = (int)((sleepMilli-Math.floor(sleepMilli))*1000000);    
                Thread.sleep((long)Math.floor(sleepMilli),sleepNano);
            }
        }
    }
}